package com.cltech.frame.specification.utils;

import com.cltech.frame.specification.PredicateBuilder;
import com.cltech.frame.specification.Sorts;
import com.cltech.frame.specification.Specifications;
import com.cltech.util.DateStyle;
import com.cltech.util.DateUtil;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;


/**
 * @author:
 * @description:
 * @create:2019-04-09 15:55
 */
public final class SpecificationSortUtil {
    public final static String SEARCHS = "searchs";
    public final static String ORDERS = "orders";

    public static final Map<String, Object> creat(Map<String, String[]> queryParameters, Class<?> clazz) {
        Map<String, Object> map = new HashMap<>();
        Sorts.Builder sortsBuilder = Sorts.builder();
        PredicateBuilder predicateBuilder = Specifications.and();
        try {
            Map<String, Class> columns = getColumnAndType(clazz);
            Map<String, List<Condition>> specifications = assembleSpecification(queryParameters, columns);
            List<Condition> searchs = specifications.get(SEARCHS);
            for (Condition search : searchs) {
                QueryRuleEnum queryRuleEnum = search.getQueryRuleEnum();
                String column = search.getColumn();
                Object value = search.getValue();
                if (QueryRuleEnum.STARTS_WITH == queryRuleEnum) {
                    predicateBuilder.like(column, "%" + value);
                }
                if (QueryRuleEnum.END_SWITH == queryRuleEnum) {
                    predicateBuilder.like(column, value + "%");
                }
                if (QueryRuleEnum.LIKE == queryRuleEnum) {
                    predicateBuilder.like(column, "%" + value + "%");
                }
                if (QueryRuleEnum.NOT_LIKE == queryRuleEnum) {
                    predicateBuilder.notLike(column, "%" + value + "%");
                }
                if (QueryRuleEnum.IN == queryRuleEnum) {
                    predicateBuilder.in(column, value);
                }
                if (QueryRuleEnum.NOT_IN == queryRuleEnum) {
                    predicateBuilder.notIn(column, value);
                }
                if (QueryRuleEnum.GT == queryRuleEnum) {
                    predicateBuilder.gt(column, (Comparable<Object>) value);
                }
                if (QueryRuleEnum.GE == queryRuleEnum) {
                    predicateBuilder.ge(column, (Comparable<Object>) value);
                }
                if (QueryRuleEnum.LT == queryRuleEnum) {
                    predicateBuilder.lt(column, (Comparable<Object>) value);
                }
                if (QueryRuleEnum.LE == queryRuleEnum) {
                    predicateBuilder.le(column, (Comparable<Object>) value);
                }
                if (QueryRuleEnum.NE == queryRuleEnum) {
                    predicateBuilder.ne(column, value);
                }
                if (QueryRuleEnum.EQ == queryRuleEnum) {
                    predicateBuilder.eq(column, value);
                }
                map.put(SEARCHS, predicateBuilder.build());
            }
            List<Condition> orders = specifications.get(ORDERS);
            for (Condition order : orders) {
                QueryRuleEnum queryRuleEnum = order.getQueryRuleEnum();
                Object value = order.getValue();
                if (QueryRuleEnum.ORDER_BY_ASC == queryRuleEnum) {
                    sortsBuilder.asc((List<String>)value);
                }
                if (QueryRuleEnum.ORDER_BY_DESC == queryRuleEnum) {
                    sortsBuilder.desc((List<String>)value);
                }
                map.put(ORDERS, sortsBuilder.build());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 仅支持两层关联查询,超过两层的查询请自写SQL查询.
     *
     * @param clazz
     * @return
     * @throws Exception
     */
    private static Map<String, Class> getColumnAndType(Class<?> clazz) throws Exception {
        Map<String, Class> map = new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (isBaseClass(field.getType())) {
                map.put(field.getName(), field.getType());
            } else if (!isBaseClass(field.getType()) && !isCollectionClass(field.getType())) {
                Class embeddedClazz = field.getType();
                Field[] embeddedFields = embeddedClazz.getDeclaredFields();
                for (Field embeddedField : embeddedFields) {
                    if (isBaseClass(embeddedField.getType())) {
                        map.put(field.getName() + "." + embeddedField.getName(), embeddedField.getType());
                    }
                }
            } else if (isCollectionClass(field.getType())) {
                Type genericType = field.getGenericType();
                System.out.println(genericType);
                if (genericType == null) {
                    continue;
                }
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    //得到泛型里的class类型对象
                    Class<?> accountPrincipalApproveClazz = (Class<?>) pt.getActualTypeArguments()[0];
                    Field[] accountPrincipalApproveFields = accountPrincipalApproveClazz.getDeclaredFields();
                    for (Field accountPrincipalApproveField : accountPrincipalApproveFields) {
                        if (isBaseClass(accountPrincipalApproveField.getType())) {
                            map.put(field.getName() + "." + accountPrincipalApproveField.getName(), accountPrincipalApproveField.getType());
                        }
                    }
                }
            }
        }
        return map;
    }

    private static Map<String, List<Condition>> assembleSpecification(Map<String, String[]> queryParameters, Map<String, Class> columnAndTypes) throws Exception {
        Map<String, List<Condition>> map = new HashMap<>();
        List<Condition> conditions = new ArrayList<>();
        List<Condition> orders = new ArrayList<>();
        for (Map.Entry<String, String[]> queryParameter : queryParameters.entrySet()) {
            String key = queryParameter.getKey();
            String value = queryParameter.getValue()[0];
            if (key.endsWith(QueryRuleEnum.STARTS_WITH.getValue())) {
                String column = key.substring(0, key.length() - 10);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.STARTS_WITH);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.STARTS_WITH));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.END_SWITH.getValue())) {
                String column = key.substring(0, key.length() - 8);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.END_SWITH);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.END_SWITH));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.LIKE.getValue())) {
                String column = key.substring(0, key.length() - 4);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.LIKE);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.LIKE));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.NOT_LIKE.getValue())) {
                String column = key.substring(0, key.length() - 7);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.NOT_LIKE);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.NOT_LIKE));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.IN.getValue())) {
                String column = key.substring(0, key.length() - 2);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.IN);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.IN));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.NOT_IN.getValue())) {
                String column = key.substring(0, key.length() - 5);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.NOT_IN);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.NOT_IN));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.GT.getValue())) {
                String column = key.substring(0, key.length() - 11);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.GT);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.GT));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.GE.getValue())) {
                String column = key.substring(0, key.length() - 12);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.GE);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.GE));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.LT.getValue())) {
                String column = key.substring(0, key.length() - 8);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.LT);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.LT));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.LE.getValue())) {
                String column = key.substring(0, key.length() - 9);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.LE);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.LE));
                    conditions.add(condition);
                }
            } else if (key.endsWith(QueryRuleEnum.NE.getValue())) {
                String column = key.substring(0, key.length() - 8);
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.NE);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.NE));
                    conditions.add(condition);
                }
            } else if (key.equals(QueryRuleEnum.ORDER_BY_ASC.getValue())) {
                Condition condition = new Condition();
                condition.setColumn(value);
                condition.setQueryRuleEnum(QueryRuleEnum.ORDER_BY_ASC);
                condition.setValue(Arrays.asList(value.split(",")));
                orders.add(condition);
            } else if (key.equals(QueryRuleEnum.ORDER_BY_DESC.getValue())) {
                Condition condition = new Condition();
                condition.setColumn(value);
                condition.setQueryRuleEnum(QueryRuleEnum.ORDER_BY_DESC);
                condition.setValue(Arrays.asList(value.split(",")));
                orders.add(condition);
            } else {
                String column = key;
                Class clazz = columnAndTypes.get(column);
                if (null != clazz) {
                    Condition condition = new Condition();
                    condition.setColumn(column);
                    condition.setQueryRuleEnum(QueryRuleEnum.EQ);
                    condition.setValue(getQueryValue(value, clazz.getName(), QueryRuleEnum.EQ));
                    conditions.add(condition);
                }
            }
        }
        map.put(SEARCHS, conditions);
        map.put(ORDERS, orders);
        return map;
    }

    private static Object valueConversion(String value, String type, QueryRuleEnum queryRuleEnum) throws ParseException {
        Object temp;
        switch (type) {
            case "class java.lang.Integer":
                temp = Integer.parseInt(value);
                break;
            case "class java.math.BigDecimal":
                temp = new BigDecimal(value);
                break;
            case "class java.lang.Short":
                temp = Short.parseShort(value);
                break;
            case "class java.lang.Long":
                temp = Long.parseLong(value);
                break;
            case "class java.lang.Float":
                temp = Float.parseFloat(value);
                break;
            case "class java.lang.Double":
                temp = Double.parseDouble(value);
                break;
            case "class java.util.Date":
                temp = getDateQueryByRule(value, queryRuleEnum);
                break;
            default:
                temp = value;
                break;
        }
        return temp;
    }

    private static Object getQueryValue(String value, String type, QueryRuleEnum queryRuleEnum) throws ParseException {
        if (!"".equals(value)) {
            if (QueryRuleEnum.IN.equals(queryRuleEnum) || QueryRuleEnum.NOT_IN.equals(queryRuleEnum)) {
                String[] values = value.split(",");
                List<Object> temps = new ArrayList<>();
                for (String val : values) {
                    if (!"".equals(val)) {
                        temps.add(valueConversion(val, type, queryRuleEnum));
                    }
                }
                return temps;
            } else {
                return valueConversion(value, type, queryRuleEnum);
            }
        }
        return null;
    }

    private static Date getDateQueryByRule(String value, QueryRuleEnum rule) throws ParseException {
        Date date = null;
        if (value.length() == 10) {
            if (rule == QueryRuleEnum.GE
                    || rule == QueryRuleEnum.GT) {
                //比较大于
                DateUtil.StringToDate(value + " 00:00:00", DateStyle.YYYY_MM_DD_HH_MM_SS);
            } else if (rule == QueryRuleEnum.LE
                    || rule == QueryRuleEnum.LT) {
                //比较小于
                DateUtil.StringToDate(value + " 23:59:59", DateStyle.YYYY_MM_DD_HH_MM_SS);
            }
        }
        if (date == null) {
            DateUtil.StringToDate(value, DateStyle.YYYY_MM_DD_HH_MM_SS);
        }
        return date;
    }

    private static boolean isBaseClass(final Class clazz) {
        if (clazz.isPrimitive() || Number.class.isAssignableFrom(clazz) || clazz.isPrimitive()
                || String.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz)) {
            return true;
        }
        return false;
    }

    private static boolean isCollectionClass(final Class clazz) {
        if (Collection.class.isAssignableFrom(clazz)) {
            return true;
        }
        return false;
    }
}
